#include "ClientController.h"
#include "bullet.h"
#include "spaceship.h"

bool clientOneAsteroid = true;

ClientController::ClientController(Model *m, View *v, NetworkPat* net, AsteroidFactory *factory, SpaceShipFactory *sFactory): Controller(m,v)
{
  mModel = (ClientModel*)m;
  mView = (ClientView*)v;
  _net = net;
  mModel->SetCurrentController(this);
  newGameState = GameState::ClientPlay;
  mBulletLimit = 0;
  asteroidFactory = factory;
  ShouldReset = true;
  this->sFactory = sFactory;
  physics.SetWorldSize(clientWorldX, clientWorldY);
  firstpass = true;
}

ClientController::~ClientController(void)
{
}

bool ClientController::ShouldCreateAsteroid(float* x, float* y, float* rotation, float* velocity)
{

  //BIG_TODO : THIS IS A BLOCK THAT USES A LOT OF RANDOM NUMBERS!!!

	//random number, generate an asteroid every 50 frames or so

	//if we should, decide where.
	int probability = randNums.GetRandomNumber(1, 30);
	if(probability < 20)
		return false;

	//now we know to generate an asteroid.  Where to generate it?
	float rot = 0.0;
	int quadrant = randNums.GetRandomNumber(1, 20);
	quadrant = quadrant % 4;
	rot = (float)randNums.GetRandomNumber(2, 4);
	*rotation = rot / 10;

	switch(quadrant){
		case 0:
			//left side
			*x = -50;
			*y = (float)randNums.GetRandomNumber(1, WORLD_SIZE_Y);
			*rotation = 0;
			//rotation should be between 0 and .5
			break;

		case 1:
			//top
			*x = (float)randNums.GetRandomNumber(1, WORLD_SIZE_X);
			*y = -50;
			*rotation += .25;			
			break;

		case 2:
			//right
			*x = WORLD_SIZE_X + 100;
			*y = (float)randNums.GetRandomNumber(1, WORLD_SIZE_Y);
			*rotation += .5;
			break;

		case 3:
			//bottom
			*x = (float)randNums.GetRandomNumber(1, WORLD_SIZE_X);
			*y = WORLD_SIZE_Y + 100;
			*rotation -= .5;
			break;
	}

	*velocity = (float)randNums.GetRandomNumber(1, 6);
	return true;
}

GameState ClientController::Update(void)
{
  if(firstpass)
  {
    GlobalDebug::GetInstance()->DbgOut("first pass");
    firstpass = false;
  }
	//do we need to initialize?
  if(this->ShouldReset)
  {
    physics.Init();
    GameObject* s;
    for(int i = 0; i < _net->GetCount(); ++i)
    {
      s = this->sFactory->GetNewObject();
      s->SetController(this);
      s->SetID(i + 1);
      s->GetMobile()->SetValue(PhysicsComponent::PositionX, 200);
      s->GetMobile()->SetValue(PhysicsComponent::PositionY, 200 + 50 * i);
      this->mModel->AddNewObject(s, i + 1 == _net->GetId() ? clientObjectType::C_PLAYERSPRITE : clientObjectType::C_SPRITE);
    }
    randNums.Seed(_net->GetSeed());
    /*physicsWorld.AddMobile(s->GetMobile());*/
    ShouldReset = false;
  }

	//game over?
  GameObject* player = mModel->GetPlayerObject();
  if(player->IsDead())
  {
    //delete everything in the game state
    this->mModel->Reset();
    this->ShouldReset = true;
    return GameState::MainMenu;
  }	

  #ifndef NO_ASTEROIDS
  float xa, ya, ra, va;
  if(ShouldCreateAsteroid(&xa, &ya, &ra, &va))
  {
    GameObject* a = this->asteroidFactory->GetNewObject();
    a->GetMobile()->SetValue(PhysicsComponent::PositionX, xa);
    a->GetMobile()->SetValue(PhysicsComponent::PositionY, ya);
    a->GetMobile()->SetValue(PhysicsComponent::Rotation, ra);
    a->GetMobile()->SetValue(PhysicsComponent::GeneralVelocity, va);
    mModel->AddNewObject(a, clientObjectType::C_SPRITE);
  }
  #endif

  if(clientOneAsteroid)
  {
    GameObject* a = this->asteroidFactory->GetNewObject();
    a->GetMobile()->SetValue(PhysicsComponent::PositionX, 400);
    a->GetMobile()->SetValue(PhysicsComponent::PositionY, 400);
    a->GetMobile()->SetValue(PhysicsComponent::Rotation, .25);
    a->GetMobile()->SetValue(PhysicsComponent::GeneralVelocity, 2);
    a->SetController(this);
    mModel->AddNewObject(a, clientObjectType::C_SPRITE);
    clientOneAsteroid = false;
  }

  // update object state
  mModel->UpdateObjects();
  this->ProcessMessages();

  // update physics
  physics.Update();

	// process my own messages in case I need to react to something that happened in 
	// physics or something
  this->ProcessMessages();

	// tell the view where the player is so that it can draw correctly
  mView->SetPlayerPosition(
    player->GetMobile()->GetValue(PhysicsComponent::PositionX),
    player->GetMobile()->GetValue(PhysicsComponent::PositionY),
    clientWorldX, clientWorldY);

  // handle collisions:
  // now the state of everything is updated, let's draw the current scene
  mView->DrawScreen();

  theReaper.DoWhatYouDo();
	
  return this->newGameState;
}

void ClientController::HandleMessages(std::list<Message*> *l)
{
  std::list<Message*>* l2 = _net->Recv();
  if(l->empty() && l2->empty())
    return;
  Message* msg;
  GameObject* obj;
  if(!l->empty())
  {
    msg = l->front();
    l->pop_front();

    obj = mModel->GetPlayerObject();
    assert(obj != NULL);
    while(msg != NULL)
    {
      if(msg->GetMessageType() == MessageType::USER_INPUT)
      {
        if(msg->GetMessageContent() == GameAction::QUIT)
        {
          Message* temp = new Message(MessageType::SYSTEM, NetMessage::LEAVE);
          temp->SetMessageId(_net->GetId());
          _net->Send(temp);
          theReaper.DoomObject(obj);
        }
        else
        {
          msg->SetMessageId(_net->GetId());
         _net->Send(msg);
        }
      }

      if(l->empty())
      {
        msg= NULL;
      }
      else
      {
        msg = l->front();		
        l->pop_front();
      }
    }
  }
  if(l2->empty())
    return;
  msg = l2->front();
  l2->pop_front();
  while(msg != NULL)
  {
    if(msg->GetMessageType() == MessageType::USER_INPUT)
    {
      obj = mModel->GetObjectByID(msg->GetMessageId());
      if(obj == NULL)
      {
        //problem
        break;
      }
      obj->SendMessage(msg);
    }
    else if(msg->GetMessageType() == MessageType::SYSTEM)
    {
      if(msg->GetMessageContent() == NetMessage::LEAVE)
      {
        _net->SetCount(_net->GetCount() - 1);
        theReaper.DoomObject(mModel->GetObjectByID(msg->GetMessageId()));
        delete msg;
      }
      else if(msg->GetMessageContent() == NetMessage::WIN)
      {
        newGameState = GameState::GameOver;
        _net->SetWin(true);
        delete msg;
      }
      else if(msg->GetMessageContent() == NetMessage::LOSE)
      {
        newGameState = GameState::GameOver;
        _net->SetWin(false);
        delete msg;
      }
    }

    if(l2->empty())
    {
      msg = NULL;
    }
    else
    {
      msg = l2->front();
      l2->pop_front();
    }
  }
  assert(l->size() == 0);
}

void ClientController::PassMessage(Message* msg)
{
  messages.push_back(msg);
}

void ClientController::ProcessMessages()
{
  while(messages.size() > 0)
  {
    Message* msg = messages.front();
    messages.pop_front();

    if(msg->GetMessageType() == MessageType::GAME_OBJECT_ACTION)
    {
      if(msg->GetMessageContent() == MessageContent::CREATE_OBJECT)
      {
        GameObject* obj = (GameObject*)msg->GetExtended();
        assert(obj != NULL);
        this->mModel->AddNewObject(obj, clientObjectType::C_PRIMITIVE);
        delete msg;
      }
      else if(msg->GetMessageContent() == MessageContent::DESTROY_OBJECT)
      {
        GameObject* obj = (GameObject*)msg->GetExtended();
        if(obj->GetType() == ObjectType::ASTEROID)
        {
          Asteroid* ast = (Asteroid*)obj;
          if(ast->GetKiller() == _net->GetId())
          {
            int newScore = _net->IncrementLocalScore(100);
            if(newScore >= WINNING_SCORE)
            {
              Message* winMsg = new Message(MessageType::SYSTEM, NetMessage::WIN);
              winMsg->SetMessageId(_net->GetId());
              _net->Send(winMsg);
            }
          }
        }
        mModel->DestroyObject(obj->GetID());
        theReaper.DoomObject((GameObject*)msg->GetExtended());
        delete msg;
      }
      else if(msg->GetMessageContent() == MessageContent::SHOULD_RENDER)
      {
        mView->PassMessage(msg);
      }
      else if(msg->GetMessageContent() == MessageContent::DO_PHYSICS)
      {
        physics.PassMessage(msg);
      }
    }
  }
  assert(messages.size() == 0);
}
